/*
Copyright (c) 2013 Mihail Volkov

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

#ifndef CONNECTION_H
#define CONNECTION_H

#include <QtNetwork/QTcpSocket>
#include <QTimer>
#include <QFile>
#include <deque>

/// @brief HtMd protocol major version supported by this client build.
/// Protocols with different major versions are not compatible in
/// either direction.
#define HTMD_PROTO_VER_MAJOR 1

/// @brief HtMd protocol minor version supported by this client build.
/// Protocols are fully backwards compatible with protocols of same
/// major version and lesser minor versions.
#define HTMD_PROTO_VER_MINOR 0

/// @brief A HtMd connection handler. Singleton.
class HtMdConnection: public QObject
{
	friend class Unit;
	friend class HtMdConsoleApp;

	Q_OBJECT

	static HtMdConnection *spInstance;
	enum
	{
		S_DISCONNECTED,
		S_CONNECTING,
		S_CONNECTED,
		S_DISCONNECTING
	} mState;
	QTcpSocket mSocket; // the core thing of our enterprise
	QTimer mTimer;

	QString mConnectionError;
	// ^reason for connection failure or disconnect
	int mPacketMaxSize,mInPacketSize,
		mOutPacketSofar;
	QByteArray mInPacket;
	bool mHelloReceived,mInPacketWriting;
	// ^reported by app

	struct AuxPacket: public QByteArray
	{
		bool isLenPacket;

		AuxPacket (): isLenPacket (false) {}
		AuxPacket (const QByteArray &src,bool lenPacket=false):
			QByteArray (src), isLenPacket (lenPacket) {}
	};

	std::deque<AuxPacket> mOutgoingPackets;
	void outPacketDispatched ();

	void tryBeginWriteNextPacket ();
	void postPacket (QByteArray packet);
	void packetReceived (QByteArray packet);

	qint64 mFileSofar,mFileTotal;
	QFile mFile;
	bool mFileUploading, // true if uploading, false if downloading
		mInFileOperation; // true if inside file operation
		// (that is, dialog and up/downloading progress)
	int mOutFilePacketsPending;

	void postClientFileCancel ();
	void postClientFileDownloadConfirm (QString actualName);
	void postClientFileUploadConfirm (QString actualName,int size);

public:
	HtMdConnection (QObject *parent=NULL);
	~HtMdConnection ();
	static HtMdConnection *instance ()
	{
		return spInstance;
	}

	/// @brief Return description of disconnection or connection
	/// failure reason
	static QString getDisconnectionReason ();

	/// @brief Initiate connection attempt according to APP settings.
	/// "connected" is emitted on success, "disconnected" on failure.
	static void connect ();
	/// @brief Initiate disconnection attempt. "disconnected" is
	/// emitted on success.
	static void disconnect ();

	static unsigned decodeUint (const QByteArray &ba,int idx);
	static void serializeUint (QByteArray &ba,int idx,unsigned theUint);
	static QByteArray serializeUint (unsigned theUint);

	void postClientCommandBegin (unsigned unitId,int totalLen);
	void postClientCommandChunk (unsigned unitId,QByteArray chunk);
	void postClientCommandCancel (unsigned unitId);
	void postClientUnitTurnIn (unsigned unitId);
	void postClientUnitSuspend (unsigned unitId);

	bool isConnected ();

private slots:
	void socketConnected ();
	void socketDisconnected ();
	void socketError (QAbstractSocket::SocketError);
	void handleConnectionFailed ();
	void bytesRead ();
	void bytesWritten (qint64 bytes);

	void fileConfirmed (QString fileName,bool upload);
	void fileCancelled ();
	void timerTick ();

signals:
	void connected ();
	void disconnected ();
};

#define HTMD_CONN (*HtMdConnection::instance ())

#endif // CONNECTION_H
